iT邦幫忙

0

【Karman.js】嚴格把關參數的驗證引擎! - 03

  • 分享至 

  • xImage
  •  

先回過頭來複習一下,在第一章有提到的 defineAPI 裡,會有一個 payloadDef 物件,專門用來定義 FinalAPI 的第一個參數 payload 需要哪些屬性,其中 {} 物件型別的 payloadDef,除了能夠以 key 定義 payload 需求參數名以外,value 還能夠是另一個物件來描述該參數的必填非必填(required)、驗證規則(rules)、預設值(defaultValue)以及使用位置(position),而當中的 requiredrules 將會與驗證引擎息息相關。

參數驗證引擎

參數驗證引擎是 Karman 內建的一個模組,負責解析 payloadDefrulesrequired,並在 FinalAPI 發起請求時驗證 payload 是否符合規則。

啟動驗證引擎,需要透過 validation 屬性來設定,取決於你要批次或單獨啟動 FinalAPI 的參數驗證,可以被設置在包括 defineKarmandefineAPI 與 FinalAPI 的第二個參數 config 裡,且設置的位置同樣會遵循卡門樹的繼承原則。

可以設定 validation 來開啟或關閉驗證引擎的地方:

const karman = defineKarman({
    // ...
    validation: true, // 開啟此節點以下的所有參數驗證
    api: {
        someAction: defineAPI({
            // ...
            validation: false // 關閉這支 FinalAPI 的參數驗證
        })
    }
})

const [resPromise] = karman.someAction(null, {
    validation: true // FinalAPI 使用時再一次啟用參數驗整
})
// ...

設定驗證規則

在 Karman 中,有許多內建的規則可以使用,其中大部分都會自動生成錯誤訊息,並點明驗證失敗的參數,除了特別複雜或特殊的規則,不然通常情況下不太會需要使用到客製化的驗證函式。

payloadDef 中,若想設定參數的驗證規則需要透過 rules 屬性,但若是想設定參數為必填需要通過 required,且所有參數預設情況下都會是非必填參數,若是想讓參數為必填,即不為 null 或是 undefined,需要將 required 屬性設置為 true

const requiredTest = defineAPI({
    // ...
    validation: true,
    payloadDef: {
        param01: {
            required: true
        }
    }
})

requiredTest() // Uncaught ValidationError: Parameter 'param01' is required, but received 'undefined'.

rules 會決定要用甚麼規則去驗證接收到的參數,以下是 Karman 當中可以使用的驗證規則:

字串規則

以字串描述參數的型別,除了基本型別("string""number"...等)外,也會有一些特殊型別:

  • "char":字符,長度為 1 的字串
  • "int":整數
  • "object":廣義上的物件
  • "object-literal":以 {} 構成的物件
  • "array":陣列

其餘內建型別可以參考官方文件

除了內建的型別外,後續還會介紹 Karman 的一個核心叫「Schema API」,透過這功能訂定出來的 schema 也能作為字串規則使用,之後會再詳細介紹。

另外,字串規則還支援一種陣列語法,類似其他強型別語言宣告陣列時的語法,如 C# 可以用 int[] 宣告一個整數陣列,使用這個語法作為驗證規則時,參數的型別必定是一個陣列,只是可以額外去規範陣列內每個元素的型別,並且還能透過以下的語法,去限制該陣列的長度:

<type>[]               # <type> 陣列
<type>[<equal>]        # 長度 <equal> 的 <type> 陣列
<type>[<min>:]         # 最小長度 <min> 的 <type> 陣列
<type>[:<max>]         # 最大長度 <max> 的 <type> 陣列
<type>[<min>:<max>]    # 最大長度 <max>、最小長度 <min> 的 <type> 陣列

現在,嘗試看看以字串規則驗證參數,並記得將 validation 設置為 true 來開啟驗證引擎,另外,這邊多配置了個 onError hooks,並在裡頭返回了非 undefinednull 的值,使下面 FinalAPI 在調用時可以不被錯誤給中斷執行:

關於 hooks 的機制會在後續章節詳細說明。

const test = defineAPI({
    validation: true,
    payloadDef: {
        param01: {
            rules: "char"
        },
        param02: {
            rules: "int"
        },
        param03: {
            rules: "string[]"
        },
        param04: {
            rules: "char[6]"
        },
    },
    onError(err) {
        console.error(err)
        return 1
    }
})


test({ param01: "K" })                                 // valid
test({ param01: "Karman" })                            // ValidationError: Parameter 'param01' should be 'char' type, but received 'Karman'.
test({ param02: 10 })                                  // valid
test({ param02: 0.1 })                                 // ValidationError: Parameter 'param02' should be 'int' type, but received '0.1'.
test({ param03: ["Karman", "Victor"] })                // valid
test({ param03: ["Karman", "Victor", 6] })             // ValidationError: Parameter 'param03[2]' should be 'string' type, but received '6'.
test({ param04: ["K", "a", "r", "m", "a", "n"] })      // valid
test({ param04: ["K", "a", "r", "m", "a", "n", "!"] }) // ValidationError: Parameter 'param04.length' should be equal to '6', but received '7'.
test({ param04: "Karman" })                            // ValidationError: Parameter 'param04' should be 'array' type, but received 'Karman'.

類別規則

在類別規則中,你可以用一個 class 或建構函式做為驗證規則,驗證引擎會以 instanceof 對參數進行驗證:

const instanceTest = defineAPI({
    validation: true,
    payloadDef: {
        param01: {
            rules: Date
        }
    }
})

instanceTest({ param01: new Date(1995, 5, 27) }) // valid
instanceTest({ param01: "1995-06-27" })          // Uncaught ValidationError: Parameter 'param01' should be instance of 'Date', but received '1995-06-27'.

自定義驗證函式

若是要使用客製化的驗證函式,需要使用到 defineCustomValidator 這支函式,它只接受一個參數,即為你要使用的自定義驗證邏輯的函式,這函式會接收兩個參數,第一個是參數名稱,第二個是參數的值,你可以對第二個參數進行驗證,不論驗證通過與否,皆不須返回任何值,只需在驗證未通過時直接拋出錯誤就好,並且 Karman 有提供了一個 ValidationError 類別,是驗證引擎預設拋出的錯誤類型,你可以藉由拋出此錯誤來統一所有驗證失敗時的錯誤訊息,以便後續的程式判讀:

import { defineAPI, defineCustomValidator, ValidationError } from "@vic0627/karman"

const sortValidator = defineCustomValidator((param, value) => {
    if (value !== "asc" || value !== "desc")
        throw new ValidationError(`Parameter '${param}' must be 'asc' or 'desc' but received '${value}'.`)
})

const validatorTest = defineAPI({
    validation: true,
    payloadDef: {
        param01: {
            rules: sortValidator
        }
    }
})

validatorTest({ param01: "aesc" }) // Uncaught ValidationError: Parameter 'param01' must be 'asc' or 'desc' but received 'aesc'.

正則表達式

以正則表達式做為驗證規則時,接受兩種規格的參數傳入,一種是一般的正則表達式,另一種是一個物件,包含了正則表達式與錯誤訊息:

const emailRegexp = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

const regexpTest = defineAPI({
    validation: true,
    payloadDef: {
        param01: {
            rules: emailRegexp
        },
        param02: {
            rules: { regexp: emailRegexp, errorMessage: "wrong email format" }
        }
    },
    onError(err) {
        console.error(err)
        return 1
    }
})
    
const wrongEmail = "karman~"
regexpTest({ param01: wrongEmail }) // ValidationError: Parameter 'param01' validation failed, but received 'karman~'.
regexpTest({ param02: wrongEmail }) // ValidationError: wrong email format

參數描述符

此種規則主要以物件構成,可以規範包括最大值、最小值、相等值與測量屬性等,以下是可用的屬性與說明:

  • min:最小值,可與 max 一同設定。
  • max:最大值,可與 min 一同設定。
  • equality:相等值,優先權最高,若是有設定這屬性,minmax 將被忽略。
  • measurement:要以什麼屬性作為量測單位,預設是 "self",也就是測量參數值本身,而其他常見屬性有 "length""size"...等等。

驗證引擎會先在參數上尋找你所設定的屬性,若是找不到,例如 "length" 屬性不存在於 number 類型中,這時 Karman 將會丟出警告訊息,並以 undefined 作為後續驗證的值,因此,此驗證規則通常不會單獨使用,而是會配合規則集合設定類型的前置條件,當通過前置規則的驗證時,才會進行範圍的驗證。

const paramDescriptorTest = defineAPI({
    validation: true,
    payloadDef: {
        param01: {
            rules: { min: 0, max: 1 }
        },
        param02: {
            rules: { equality: 5, measurement: "length" }
        }
    },
    onError(err) {
        console.error(err)
        return 1
    }
})

paramDescriptorTest({ param01: 2 })
// ValidationError: Parameter 'param01' should be within the range of '0' and '1', but received '2'.
paramDescriptorTest({ param02: 2 })
// [karman warn] Cannot find property "length" on "2".
// ValidationError: Parameter 'param02.length' should be equal to '5', but received 'undefined'.
paramDescriptorTest({ param02: "karman!" })
// ValidationError: Parameter 'param02.length' should be equal to '5', but received '7'.

規則集合

規則集合,或稱複合規則,可一次設定多組規則,依照不同的集合類型,會有不同的通過驗證條件,在 Karman 中規則集合會分成下列兩項:

  • 聯集規則 UnionRules:當參數滿足聯集規則中的任一項規則,即通過驗證。
  • 交集規則 IntersectionRules:當參數滿足交集規則中的所有規則,即通過驗證。

而要定義規則集合的方式有兩種,第一種是直接在 rules 傳入規則的陣列,這會在初始化時自動轉換成交集規則,第二種是使用 Karman 提供的 API defineUnionRulesdefineIntersectionRules

import { defineAPI, defineUnionRules, defineIntersectionRules } from "@vic0627/karman"

const ruleSetTest = defineAPI({
    validation: true,
    payloadDef: {
        param01: {
            rules: ["int", { min: 1 }]
        },
        // param01 與 param02 這兩種方法等效
        param02: {
            rules: defineIntersectionRules("int", { min: 1 })
        },
        param03: {
            rules: defineUnionRules("string", "number")
        }
    },
    onError(err) {
        console.error(err)
        return 1
    }
})

ruleSetTest({ param01: -1 })
// ValidationError: IntersectionRules
// [1] Parameter 'param01' should be greater than or equal to '1', but received '-1'
ruleSetTest({ param03: false })
// ValidationError: UnionRules
// [0] Parameter 'param03' should be 'string' type, but received 'false'.
// [1] Parameter 'param03' should be 'number' type, but received 'false'.

以上就是參數驗證引擎的介紹了,至於下一章節要介紹什麼我還需要再思考一下,另外,在撰寫這些文章的同時,我也會一邊驗證套件的實際行為是否符合文章所寫內容(應該寫測試的),所以更新進度可能會稍微緩慢一些。


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言